<?php
/**
 * File docblock description
 *
 * Some Sample stuff
 * @author Joseph Montanez <sutabi@gmail.com>
 * @version 0.0.3
 * @package MooDom
 */

/**
 * @package MooDom
 */
class MooDom {
    function __construct($filename) {
        $this->html = new DOMDocument();
        if(is_file($filename)) {
            $this->html->loadHTMLFile($filename);
        } else {
            $this->html->loadHTML($filename);
        }
    }

    public function element($tagname, array $properties = array()) {
        $element = $this->evalHtml('<' . $tagname . '> </' . $tagname . '>');
        return $element;
    }

    public function evalHtml($html) {
        $dom = new DOMDocument();
        @$dom->loadHTML($html);
        $domnode = $dom->getElementsByTagName('body');
        $domnode = $domnode->item(0)->childNodes->item(0);
        $domnode = $this->html->importNode($domnode, true);
        $domnode = new MooDomNode($this, $domnode);
        return $domnode;
    }

    /**
     * @return string
     */
    public function toHtml() {
        return $this->html->saveXML();
    }

    public function __toString() {
        return $this->toHtml();
    }

    /**
     * @param string $name
     * @return MooDomNode|DOMDocument
     */
    public function __get($name) {
        if($name == 'body') {
            $bodies = $this->html->getElementsByTagName('body');
            $node = new MooDomNode($this, $bodies->item(0));
            return $node;
        } else if($name == 'head') {
            $bodies = $this->html->getElementsByTagName('head');
            $node = new MooDomNode($this, $bodies->item(0));
            return $node;
        } else {
            return $this->$name;
        }
    }
}

/**
 * @package MooDomNode
 */
class MooDomNode {
    function __construct(&$owner, &$node) {
        $this->owner = $owner;
        $this->node = $node;
        $this->mooid = uniqid();
        $this->setProperty('mooid', $this->mooid);
    }

    public function toCssSelector($value) {
        if(strstr($value, '/')) {
            return $value;
        } else {
            $value = preg_replace('/(\.+)/i', '[@${1}]', $value);
        }
    }

    /**
     * @param string $attribute
     * @param string $value
     * @return array
     * @see function getElementByAttribute
     */
    private function getElementsByAttribute($attribute, $value) {
        return $this->getElements('//*[@' . $attribute . '="' . $value . '"]');
    }

    /**
     * @param string $attribute
     * @param string $value
     * @return MooDomNode
     * @see function getElementsByAttribute
     */
    private function getElementByAttribute($attribute, $value) {
        $elements = $this->getElementsByAttribute($attribute, $value);
        if(!empty($elements)) {
            return $elements[0];
        } else {
            return false;
        }
    }

    /**
     * @param string $id
     * @return MooDomNode
     */
    public function getElementById($id) {
        return $this->getElementByAttribute('id', $id);
    }


    /**
     * @param string $attribute
     * @return MooDomNode
     */
    public function erase($attribute) {
        $this->node->removeAttribute($attribute);
        return $this;
    }

    /**
     * getElements
     *
     * Using XPath:
     * <code>
     * <?php
     * // Get all <div> tags in the body
     * $elements = $document->body->getElements('//div');
     * ?>
     * </code>
     * Using Css Selectors:
     * <code>
     * <?php
     * // Get all <div> tags in the body
     * $elements = $document->body->getElements('div');
     * ?>
     * </code>
     * @param string $selector The XPath Query
     * @return array
     */
    public function getElements($selector) {
        $query = '';
        $query .= '//' . $this->node->tagName . '[@mooid="' . $this->mooid . '"]';
        $query .= $q;
        $xpath = new DOMXPath($this->owner->html);
        $rows = $xpath->query($query);
        $nodes = array();
        for($i = 0; $i < $rows->length; $i++) {
            $row = $rows->item($i);
            $nodes []= new MooDomNode($this->owner, $row);
        }
        return $nodes;
    }

    /**
     * @param string $selector
     * @return MooDomNode
     */
    public function getElement($selector) {
        $elements = $this->getElements($q);
        $element = $elements[0];
        if(!$element) {
            $htmls = $this->owner->html->getElementsByTagName('html');
            $node = new MooDomNode($this->owner, $htmls->item(0));
            $elements = $node->getElements($q);
            $element = $elements[0];
        }
        if(!$element) {
            throw new Exception('Element you want is not found');
        }
        return $element;
    }

    public function set($name, $value) {
        if($name == 'text') {
            $newtext = new DOMText($value);
            $childs = $this->node->childNodes;
            if($childs) {
                $nth = 0;
                $text = $childs->item($nth);
                while(!$childs->item($nth) instanceof DOMText) {
                    $nth++;
                    $text = $childs->item($nth);
                    if($text === null) {
                        $text = $childs->item($nth - 1);
                        break;
                    }
                }

                if($text === null) {
                    $this->node->appendChild($newtext);
                } else {
                    $this->node->replaceChild($newtext, $text);
                }
            } else {
                $this->node->appendChild($newtext);
            }
        } else if($name == 'html') {
            $this->clear();
            $node = $this->owner->evalHtml($value);
            $node->inject($this);
        } else {
            $this->setProperty($name, $value);
        }
        return $this;
    }

    public function get($name) {
        if($name == 'html') {
            $html = '';
            if($this->node->childNodes) {
                foreach($this->node->childNodes as $node) {
                    $html .= $node->ownerDocument->saveXML($node);
                }
            } else {
            }
            return $html;
        } else if($name == 'text') {
            return $this->node->textContent;
        } else if($name == 'tag') {
            return $this->node->tagName;
        } else {
            return $this->getProperty($name);
        }
    }

    public function getParent() {
        if($this->node->parentNode) {
            $parent = new MooDomNode($this->owner, $this->node->parentNode);
            $this->parent = $parent;
            return $parent;
        }
        return false;

    }

    public function copy($contents = true, $keepid = false) {
        $dom = new MooDom($this->toHtml());
        $domnode = $dom->html->getElementsByTagName('body')->item(0)->firstChild;
        $domnode = new MooDomNode($dom, $domnode);
        if(!$keepid) {
            $domnode->set('id', '');
        }
        if(!$contents) {
            $domnode->clear();
        }
        $domnode->mooid = uniqid();
        $domnode->setProperty('mooid', $domnode->mooid);
        return new MooDomNode($domnode->owner, $domnode->node);

    }

    public function setProperty($prop, $value) {
        if(!$this->node) {
            throw new Exception('Element you want is not found');
        }
        $this->node->setAttribute($prop, $value);
        return $this;
    }

    public function getProperty($prop) {
        return $this->node->getAttribute($prop);
    }

    public function getDocument() {
        return $this->node->ownerDocument;
    }

    public function toHtml() {
        $html = $this->getDocument()->saveXML($this->node);
        return $html;
    }

    public function inject($node, $location = 'bottom') {
        if($this->node->ownerDocument !== $node->node->ownerDocument) {
            if($node->node === null) {
                throw new Exception('Element you want to inject into is null');
            };
            $this->node = $node->node->ownerDocument->importNode($this->node, true);
        }
        $this->parent = $node;
        if($location == 'bottom') {
            $this->node = $node->node->appendChild($this->node);
        } else if($location == 'top') {
            $first = $node->node->firstChild;
            if($first) {
                $this->node = $node->node->insertBefore($this->node, $first);
            } else {
                $this->node = $this->inject($this->node, 'bottom');
            }
        } else if($location == 'after') {
            $sibling = $node->node->nextSibling;
            if($sibling) {
                $this->node = $node->node->parentNode->insertBefore($this->node, $sibling);
            } else {
                $this->node = $this->inject($node->node->parentNode, 'bottom');
            }
        }
        return $this;
    }

    /**
     * @param string $selector
     * @return MooDomNode
     */
    public function getFirst($selector='') {
        $first = $this->node->childNodes->item(0);
        if($first) {
            return new MooDomNode($this->owner, $first);
        } else {
            return false;
        }
    }

    /**
     * @param string $selector
     * @return MooDomNode
     */
    public function getLast() {
        $last = $this->node->childNodes->item($this->node->childNodes->length - 1);
        if($last) {
            return new MooDomNode($this->owner, $last);
        } else {
            return false;
        }
    }

    public function dispose() {
        return $this->node->parentNode->removeChild($this->node);
    }

    public function clear() {
        if($this->node->hasChildNodes()) {
            while($this->node->childNodes->length) {
                $this->node->removeChild($this->node->firstChild);
            }
        }
        return $this;
    }
}

function MooDom($filename) {
    @$document = new MooDom($filename);
    return $document;
}
?>